home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Visual Basic Source Code
/
Visual Basic Source Code.iso
/
vbsource
/
metkit
/
setup.exe
/
EXAMPLES
/
FTPCAT
/
FTPCAT.CPP
next >
Wrap
C/C++ Source or Header
|
1997-06-08
|
8KB
|
285 lines
// Copyright (C) 1996, 1997 Meta Four Software. All rights reserved.
//
// Ftp site catalog sample code
//
//! rev="$Id: ftpcat.cpp,v 1.3 1997/05/27 00:06:40 jcw Rel $"
#include "m4kit.h"
#include <io.h>
/////////////////////////////////////////////////////////////////////////////
// Property definitions
c4_ViewProp pFiles ("files");
c4_IntProp pParent ("parent"),
pSize ("size"),
pDate ("date");
c4_StringProp pName ("name");
/////////////////////////////////////////////////////////////////////////////
// Reconstruct the full path name from a subdirectory index in the tree
CString fFullPath(c4_View& dirs_, int dirNum_)
{
// Prefix all parent dir names until the root level is reached
CString path;
for (;;)
{
path = pName (dirs_[dirNum_]) + "/" + path;
if (dirNum_ == 0)
return path; // this result always has a trailing backslash
dirNum_ = (int) pParent (dirs_[dirNum_]);
}
}
/////////////////////////////////////////////////////////////////////////////
// Attempt to convert Unix date back to good ol' DOS format, i.e. 7:5:4 bits
int DecodeUnixDate(const CString& buf_)
{
static CString months = "JanFebMarAprMayJunJulAugSepOctNovDec";
// extra logic, in case no year was specified
static int limit = 0, year = 0;
if (!limit)
{
// determine the time 30 days ahead (clock may be a bit off)
time_t t1 = time(0) + 30 * 24 * 3600L;
tm* t2 = gmtime(&t1);
// remember that date as the limit date
limit = ((t2->tm_year - 80) << 9) |
((t2->tm_mon + 1) << 5) | t2->tm_mday;
// remember the default year to use if none is specified
// this may be a year to far, we'll back up later if needed
year = t2->tm_year + 1900;
}
int m = months.Find(buf_.Left(3));
if (m % 3 != 0 || buf_[3] != ' ' || buf_[6] != ' ')
return 0;
int y = buf_[9] == ':' ? year : atoi(buf_.Mid(8,4));
if (y < 1980 || y > 2080)
return 0;
int x = ((y - 1980) << 9) | ((m / 3 + 1) << 5) | atoi(buf_.Mid(4,2));
// if the date is within a year into the future, use prev year
if (x > limit && x - 512 < limit)
x -= 512;
return x;
}
/////////////////////////////////////////////////////////////////////////////
// Decode a single line of a Unix-style directory listing
// Note: there are a *lot* more systems and listing formats out there,
// but this is only an example, it works fine with several popular sites
char DecodeUnixEntry(const CString& buf_, c4_Row& entry_)
{
// assume each entry ends with: ' ' <size> ' ' <date> ' ' <name>
int n = buf_.ReverseFind(' ');
// Mac filenames can contain ' ', so check in regular place first
if (n > 55 && buf_[54] == ' ' && buf_[41] == ' ' &&
DecodeUnixDate(buf_.Mid(42, 12)) != 0)
n = 54; // date in regular position is ok, so use that instead
if (n > 40 && buf_[n-13] == ' ')
{
pName (entry_) = buf_.Mid(n+1);
switch (buf_[0])
{
case '-': // regular file
pDate (entry_) = DecodeUnixDate(buf_.Mid(n-12, 12));
n = buf_.Left(n-13).ReverseFind(' ');
if (n > 20)
{
pSize (entry_) = atol(buf_.Mid(n));
return 'f';
}
break;
case 'd': // directory, but not if name starts with '.'
if (buf_[n+1] != '.')
return 'd';
}
}
return 0;
}
/////////////////////////////////////////////////////////////////////////////
// Scan a remote ftp site and return a corresponding structure for it
//
// This code is extremely simple in terms of tcp/ip stuff, since there
// aren't any calls - everything is handled by Win95's FTP.EXE program.
//
// On the other hand, this code is pretty tricky since two pipes are
// set up to control the execution of this child program. One pipe feeds
// commands to ftp, the other reads back results and decodes each line.
//
// For some unknown reason, all server messages are lost. This is not
// critical, since the list output *does* get through, as well as any
// other output from commands. That's enough to make this thing work.
//
// The bottom line is:
//
// 1) There is no networking stuff in here, FTP.EXE does it all
// 2) This is built as a console task, so popen is neatly hidden
// 3) The result is a perfectly usable command-line utility
// 4) This only works on Win95 (perhaps also on NT 3.51 or 4.0)
// 5) The port suffix (site::port) is not implemented
c4_View fScanFTP(const char* path_, const char* usr_, const char* pw_)
{
// everything before the first '/' is the site name
CString site = path_;
site = site.SpanExcluding("/");
// start with a view containing the path (without the site prefix)
c4_View dirs;
dirs.Add(pName [path_ + site.GetLength()]);
// set up a pipe, this reminds me of the good ol' Unix days...
int ph[2];
VERIFY(_pipe(ph, 100000, 0) == 0); // allow 100k in the pipeline
VERIFY(_dup2(ph[1], 1) == 0);
VERIFY(_close(ph[1]) == 0);
// now, when the "list" file is read, its data will come from stdout
FILE* list = fdopen(ph[0], "rt");
ASSERT(list);
fprintf(stderr, "Connecting to %s ...\n", (const char*) site);
// spawn a process, connect, and prepare to pipe commands to it
FILE* cmds = _popen("ftp -n " + site, "wt");
ASSERT(cmds);
// only the child should have the write side of the pipe open
VERIFY(_dup2(2, 1) == 0);
// prepare to do some work by logging in
fprintf(cmds, "user %s %s\npwd\n", usr_, pw_);
fflush(cmds);
// read one line back right now, to make sure connection is ok
char buf [1024];
fgets(buf, sizeof buf, list);
// This loop "automagically" handles the recursive traversal of all
// subdirectories. The trick is that each scan may add new entries
// at the end, causing this loop to continue (GetSize() changes!).
int i;
for (i = 0; i < dirs.GetSize(); ++i)
{
CString path = fFullPath(dirs, i);
if (path != "/") // remove the trailing slash
path = path.Left(path.GetLength() - 1);
// send two command to the child process, the second one is
// needed to produce a trailing line we can wait on
fprintf(stderr, "%4d: %-65.65s\r", i, (const char*) path);
fputs("dir " + path + "\npwd\n", cmds);
fflush(cmds);
c4_View files;
c4_Row dir, file;
// look at each of the returned lines in turn
while (fgets(buf, sizeof buf, list))
{
CString temp = buf;
temp = temp.SpanExcluding("\r\n");
int result = atoi(temp);
if (result == 0)
{
// puts(temp);
char type = DecodeUnixEntry(temp, file);
if (type == 'd')
{
pParent (dir) = i;
pName (dir) = pName (file);
dirs.Add(dir);
}
else if (type == 'f')
{
files.Add(file);
}
}
else
{
pFiles (dirs[i]) = files.SortOn(pName);
break;
}
}
}
fprintf(stderr, "%75s\r%4d directories scanned.\n", "", i);
_pclose(cmds);
fclose(list);
// The returned object contains the entire directory tree.
// Everything is automatically destroyed when no longer referenced.
return dirs;
}
/////////////////////////////////////////////////////////////////////////////
// Try this on internet: "ftpcat ftp.winsite.com/pub/pc/win95/programr"
int main(int argc, char** argv)
{
const char* dest = argc == 3 ? argv[--argc] : "ftpcat.dat";
if (argc != 2)
{
fprintf(stderr, "Usage: FTPCAT site/path [output]\n"
" or: FTPCAT [ftp://][user:passwd@]site[/path] [output]\n");
return 1;
}
// the following logic splits up an URL into its components
CString arg = argv[1];
if (arg.Left(6).CompareNoCase("ftp://") == 0)
arg = arg.Mid(6);
CString id = "anonymous:ftpcat@any.org";
if (arg.Find('@') >= 0)
{
int n = arg.ReverseFind('@'); // use the last one!
id = arg.Left(n);
arg = arg.Mid(n + 1);
}
CString pass;
if (id.Find(':') >= 0)
{
int n = id.Find(':');
pass = id.Mid(n + 1);
id = id.Left(n);
}
// ready to scan, prepare a storage object for the results
c4_Storage storage (dest, true);
// this scans the ftp site and saves the results
c4_View view = fScanFTP(arg, id, pass)